<?php

1->熔断器
   -) '工作状态'
                                    +---------------+
                                    |    发起调用    |
                                    +---------------+
                                            |
                                  +-------------------+
                                  |   AspectJ切面拦截  |
                                  +-------------------+
                                            |
          +---------+                 +-----------+                  +---------+
          | 远程方法 | <--- '关闭' ---  |  检查熔断器 |  --- '开启' ---> | Fallback | <------+
          +---------+                 +-----------+ <---+            +---------+         |
               |                                        |                                |
               |                      +-----------+     |                                |
               |                      |  熔断检查  | ----+                                |
               |                      +-----------+                                      |
               |                            |                                            |
               |                            |                                            |
        +--------------+             +-------------+                                     |
        |  异常抛出超时  | ----------> |  上报Metrics |                                     |
        +--------------+             +-------------+                                     |
               |-------------------------------------------------------------------------+

        1. 发起调用，切面拦截
           由于熔断器是建立在服务降级的基础上，因此在前面的触发机制上和服务降级流程一模一样。在向@HystrixCommand注解修饰的方法发起调用时，将会触发由Aspect切面逻辑.

        2. 检查熔断器：当熔断状态开启的时候，直接进入fallback，不执行远程调用.

        3. 发起远程调用-异常: 进入回调函数

        4. 计算Metrics, 如果达到熔断标准, 则开启断路开关, 后续的请求直接进入fallback流程

   -) '熔断器状态 x3'
                            +------------ Open ----------+
                            ||-------------|             |
                            ||                           |
                            ||                           |
                        Half-Open -------------------- Closed

       1. [open]
           劳改中，服务在一定时间内不得发起外部调用，前来探视者一律去fallback里接待.

       2. [half-open]
           取保候审，在fallback里待的也够久了，给一个改过自新的机会，可以尝试发起真实的服务调用，但这一切都在监视下进行.

       3. [closed]
           无罪释放，上一步的调用成功了，那便关闭熔断，开始一段正常生活.

   -) '阈值判断'
       1. 在一定时间窗口内，发生异常的请求数量达到临界值
       2. 在一定时间窗口内，发生异常的请求数量占请求总数量达到一定比例



2->熔断的配置
   hystrix:
    command:
     default:
       circuitBreaker:
         requestVolumeThreshold: 5          # 熔断的前提条件（请求的数量），在一定的【时间窗口】内，请求达到【5】个以后（>5），才开始进行熔断判断
         errorThresholdPercentage: 50        # 错误【百分比】，在上面一个参数超过之后的【错误请求率】达到【50%】，进入熔断开关开启
         sleepWindowInMilliseconds: 15000    # 当熔断开启之后，经过多少秒再次进入半开状态
       metrics:
         rollingStats:
           timeInMilliseconds: 20000         # 配置【时间窗口】的时间，与上面2个配置搭配作为【熔断开启配置】(默认5000)



3->主链路
   -) '主链路原则'
       . 一切向钱看 (所有的努力都是为了'钱'!)
       . 流量 (有流量才有市场!)
       . 底线 (业务最小可用性, 如: 支付渠道中最少保证有1个可用!)
       . 故障恢复 (异常补偿机制, 降级方案)

   -) '主链路规划————导流端'

                             +-------------------------------------------------+
       +----------------+    |                   '站内搜索'                      |
       |     导流效果    |    |       推广平台 <--------|--------> 用户画像推荐     |
       |  访问量 * 转化率 |    |                       |                         |
       +----------------+    |       淘宝客  <--------|--------> 类目渠道页       |
                             |                       |                         |
                             +-----------------------|-------------------------+
                                                     |
                                                '商品详情页'
                                                     |
                      +--------------------------------------------------------------+
                      |    评论服务          UMP营销服务           SKU           库存   |
                      |   'TFS富文本详情'    图片空间(详情图, 主图)     顽兔视频空间        |
                      +--------------------------------------------------------------+

    -) '主链路替换规则'
        . 如果针对某服务'存在替代方案', 那么可以不作为主链路.

    -) '聊点别的'
        阿里系部署策略
        . 同城双活 -------> 异地短距双活 -------> 异地长距多活
        . 集团发起常规'容灾演练', 没事就切断机房!

                                    +--------------------+
                                    |   降级和熔断监控指标  |
                                    +--------------------+
                                          实时监控纬度 -------------> 并发度 (QPS / RT)
                                              |
                                            '策略'
                                              |
                    +----------------------------------------------------+
                    |                                                    |
                  [流量]                                                [减负] -------> 阈值开关 / 手动开关
          (自身流量和施加其他系统的流量(缓存/db))                    FastFail(快速失败, 减少排队)



4->隔离之————线程隔离 (Hystrix默认)
   -) '隔离三道坎'
       -  '线程池拒绝'
           这一步是线程隔离机制直接负责的，假如当前商品服务分配了10个线程，那么当线程池已经饱和的时候就可以拒绝服务，调用请求会收到Thread Pool Rejects，
           然后将被转到对应的fallback逻辑中。其实控制线程池中线程数量是由多个参数共同作用的，我们分别看一下
           . coreSize: 核心线程数 (默认10)

           . maximumSize: 设置最大允许的线程数 (默认也是10)，这个属性需要打开allowMaximumSizeToDivergeFromCoreSize之后才能生效，
                          后面这个属性允许线程池中的线程数扩展到maxinumSize个

           . queueSizeRejectionThreshold: 这个属性经常会被忽略，这是控制队列最大阈值的，Hystrix默认设置了5。即便把maximumSize改大，
                                          但因为线程队列阈值的约束，你的程序依然无法承载很多并发量。所以当你想改大线程池的时候，需要这两个属性一同增大!

           . keepAliveTimeMinutes: 这个属性和线程回收有关，我们知道线程池可以最大可以扩展到maximumSize，当线程池空闲的时候，多余的线程将会被回收，
                                   这个属性就指定了线程被回收前存活的时间。默认2分钟，如果设置的过小，可能会导致系统频繁回收/新建线程，造成资源浪费.

       -  '线程Timeout'
           我们通常情况下认为延迟只会发生在网络请求上，其实不然，在Netflix设计Hystrix的时候，就有一个设计理念：调用失败和延迟也可能发生在远程调用之前
          （比如说一次超长的Full GC导致的超时，或者方法只是一个本地业务计算，并不会调用外部方法），这个设计理念也可以在Hystrix的Github文档里也有提到。
           因此在方法调用过程中，如果同样发生了超时，则会产生Thread Timeout，调用请求被流转到fallback.

       -  '服务异常/超时'
           这就是我们前面学习的的服务降级，在调用远程方法后发生异常或者连接超时等情况，直接进入fallback.

   -) '隔离方式'
       Hystrix提供了两种线程隔离的方式，分别是线程池技术和信号量技术。这两种方式在业务流程上是一致的，
       在默认情况下，Hystrix使用线程池的方式。可以使用如下配置参数切换到信号量方式:
       execution.isolation.strategy = ExecutionIsolationStrategy.SEMAPHORE



5->大激突——————线程池VS信号量
   通常我们都采用基于线程池的实现方式，这也是最容易理解的方案。Hystrix还提供了另一种底层实现，那就是信号量隔离。
   -) '线程隔离原理'
       . 线程池技术: 它使用Hystrix自己'内建的线程池'去执行方法调用，而不是使用Tomcat的容器线程.
       . 信号量技术: 它直接使用Tomcat的容器线程去执行方法，不会另外创建新的线程，信号量只充当开关和计数器的作用。'获取到信号量的线程就可以执行方法'，
                   没获取到的就转到fallback.

   -) '性能角度分析'
       . 线程池技术: 涉及到线程的创建、销毁和任务调度，而且CPU在执行多线程任务的时候会在不同线程之间做切换，我们知道在操作系统层面CPU的线程切换是一个相对耗时的操作，
                   因此从资源利用率和效率的角度来看，'线程池技术会比信号量慢'.
       . 信号量技术: 由于直接使用Tomcat容器线程去访问方法，信号量只是充当一个计数器的作用，没有额外的系统资源消费，所以在性能方面具有明显的优势.

   -) '超时判定'
       . 线程池技术: 相当于多了一层保护机制（Hystrix自建线程），因此可以直接对“执行阶段”的超时进行判定.
       . 信号量技术: 只能等待诸如网络请求超时等“被动超时”的情况.

   -) '使用场景'
       信号量: 适用在超高并发的非外部接口调用上,
       线程隔离: 在其他场景上.



6->监控大盘————Hystryx_Turbine
   -) '大屏展示'
       . 聚合信息: 双11各路服务节点数量庞大，我们需要有一个机制来汇总每个节点的成交额信息，把成交信息聚合到一个点，并且这个聚合操作又不能影响到主链路.
       . 大盘展示: 有了聚合信息，接下来就要构建一个大盘，只从聚合后的单点位置拉取数据然后展示出来.

       可见这个监控大盘并不负责聚集成交数据，而是依靠某种后台的机制将信息聚集在一点，大盘只要展示就好了。我们如果想监控Hystrix的实时状态，
       知晓服务熔断、异常的数量变化，也可以通过这种先聚合信息后大盘展示的方法来做。


   -) 'Turbine'
                                                    +-----------+                 +-----------+
                                                    |  Turbine  | --------------> |  注册中心  |
                                                    +-----------+                 +-----------+
                                                    监控列表: 服务A
                                                          |
                                                          |
                                        +-----------------|-----------------+
          +----------------+            |                 |                 |
          | 服务B (Service1)|         Server1           Server2           Server3
          +----------------+

       Turbine其实也是一个服务节点，它正是借助Eureka的服务发现来完成信息聚合的。
       . 配置监控服务和集群: 在Turbine里我们需要配置目标服务，也就是需要Turbine实时监控的服务名称。如果应用的部署结构比较复杂，比如说分了几个大集群，
                          这时一个Turbine节点可能就无法监管这么多的服务节点了。我们可以启用多个Turbine聚合服务，每个服务指定一个集群，
                          用来聚合这个集群下所有服务节点的Hystrix状态。在默认单cluster的部署结构下，Turbine默认监管default cluster（课程也是采用默认配置）。

       . 服务发现: 连接Eureka注册中心，利用服务发现机制拉取服务节点列表，从中找到上一步中配置的指定服务都有哪些服务节点.

       . 聚合信息: 这一步聚合操作是Turbine的核心功能，它并不是让各个服务节点把自己的信息上报给Turbine，因为对服务节点来说它们并不知道自己是否在Turbine的监控名单上。
                  这一步其实是由Turbine主动发起的，从服务节点的指定"/actuator"路径下的Hystrix监控接口获取信息。

   -) '监控大盘'
       Hystrix提供了一个监控大盘的服务叫Dashboard，可以简单地通过@EnableHystrixDashboard注解直接开启，
       它会采用图形化的方式将每个服务的运行状态显示出来，它提供了两个维度的监控:
       . 单一节点监控: 通过直接访问服务节点的'/actuator'接口，获取当前节点的Hystrix监控信息。

       . Turbine聚集信息监控: 通过访问Turbine服务的'/actuator'接口，获取经过聚合后的Hystrix监控信息。

       通过大盘监控，我们就可以实时掌握服务的健康度状态，知晓哪些服务正处于熔断状态，以便及时排查问题。



7->Turbine (安全监督者)
#  我们不生产水, 我们只是大自然的搬运工.
#                                 - 农夫山泉
   -) '联想'
       问: 为啥会存在这玩意...
       答: 问得好! 话说我们有那么多的微服务集群, 那么配置了'N多降级策略', 我们怎么去'查案'获取到服务是否正常工作呢? 有没有被降级? 有没有'不法分子'呢...
       原理: Turbine其实是借助'微服务各自配置的-pom.actuator', 然后'Turbine自身服务'配置需要'展示的服务名称'即可!
            所以我们发现, 其实Turbine自己不生产数据, 只是数据的搬运工!
       缺点: 那这样固然是可以将'各个服务状态'给展示, 但是也只是'纯文字'! 看起来还是'超级费劲', 那怎么解决呢?! (接下来上好货呦!)

   -) 'pom'
       <dependencies>
          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
          </dependency>

          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
          </dependency>

          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-starter-netflix-turbine</artifactId>
          </dependency>

          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-web</artifactId>
          </dependency>

          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-actuator</artifactId>
          </dependency>
      </dependencies>

   -) '注解'
       @EnableDiscoveryClient
       @EnableHystrix
       @EnableTurbine
       @EnableAutoConfiguration

   -) '配置文件'
       . ['Turbine端服务']
       # 管理界面端口
       management.server.port=50004
       # 指定监控【哪些服务】
       # turbine.app-config=hystrix-consumer, user-info, order-info             # 集群配置
       turbine.app-config=hystrix-consumer
       # 监控【哪个集群】
       turbine.cluster-name-expression='"default"'
       # 将【端口+host】作为区分不同服务的条件（默认只使用Host）
       turbine.combine-host-port=true
       # 暴露服务, 指定从[actuator的哪个节点拉取] - 后期需要的话便于【turbine-dashboard】聚合
       turbine.instanceUrlSuffix.default=actuator/hystrix.stream
       # 集群配置服务
       # turbine.aggregator.cluster-config=default, cluster1, cluster2
       turbine.aggregator.cluster-config=default

       . ['对外暴露的服务端']
       # 关闭 [安全检查, 生产要注意!]
       management.security.enabled=true
       # 指定该服务对外暴露的接口 [一般指定降级的接口方法]
       # management.endpoints.web.exposure.include=info, health, ...       # 开放特定
       management.endpoints.web.exposure.include='*'
       # 总是对外暴露详情
       management.endpoint.health.show-details=always

   -) '启动'
       [turbine配置的'管理界面端口']
       http://192.168.76.105:50004/actuator/hystrix.stream

       是个基于'长连接 - ping', 就会'监控配置的微服务'是否发生了'降级/熔断'...



8->Turbine——Dashboard (好看的样子只为你)
   -) 'pom'
       <dependencies>
          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
          </dependency>

          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
          </dependency>

          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-actuator</artifactId>
          </dependency>
      </dependencies>

   -) '注解'
       @EnableHystrixDashboard
       @SpringBootApplication

   -) '配置文件'
       server:
         port: 5005

       spring:
         application:
           name: hystrix-dashboard

   -) '访问'
       http://ip:port/hystrix

   -) '监控路径形式'
       . 基于'hystrix-单服务': http://ip:port/actuator/hystrix.stream
       . 基于'turbine-聚合后': http://ip:port/turbine.stream


9->兄弟搭配————干活不累
                            Netflix
                               |
                             Feign
                               |
                  +-----------------------+
                  |                       |
                Ribbon                 Hystrix(大哥)
                                          |
                              +-----------------------+
                              |                       |
                         Turbine(二哥)          Turbine-Dashboard(三弟)



10->项目技巧
    -) '健康检测'
        一般在'重要服务'中添加'检测依赖', 如果不知道哪些服务是重要的话, 可以在服务pom中都添加'actuator依赖'.

    -) 'Feign'
        我们使用Feign作为'服务间调用', 那么我们想要实现'降级/熔断'的话, 有2中方式:
        - 当前内部调用:
          一般在'service层'处理调用'feign-降级/熔断'的逻辑. (指定service方法上@HystrixCommand(), 在'类内部'新建方法做处理.)

        - 其余服务调用:
          我们处理'降级/熔断'一般是哪个服务调用了'feign', 就在哪个服务里'针对性处理',(feign只用于做服务调用),
          我们可以在实际调用的服务里, 创建一个'接口继承调用feign', 然后在自己创建的接口里实现'降级/熔断'逻辑!

          也可以不做上述操作, 直接指定'feign接口的.fallback', 但是这样就有个问题: 所有调用该feign服务的方法发生了异常,
          那么就会有'同一的 [降级/熔断] 逻辑'!

    -) 'fallback'
        降级/熔断 有两种创建方式
        - '指定降级类'
           @FeignClient(value = "eureka-client", fallback = SayHystrix.class)
           使用: 直接创建'SayHystrix类', 然后实现该接口, 在里面针对方法实现'降级/熔断'逻辑.

        - '指定降级工厂'
           @FeignClient(value = "eureka-client", fallbackFactory = HystrixFallbackFactory.class)
           使用:
           public class HystrixFallbackFactory implements FallbackFactory {
               @Override
               public IHystrixService create(Throwable cause) {
                   return new IHystrixService() {
                       @Override
                       public String error(String name) {
                           return null;
                       }

                       @Override
                       public String sayHello() {
                           return null;
                       }
                   };
               }
           }

           好处: 相对'指定降级类'来说, 工厂的好处, 不局限于'单个降级类', 工厂里可以针对'多个feign'实现降级逻辑.
           缺点: 就是因为可以针对'多个feign'实现降级逻辑, 导致要实现的'降级方法太多!', 使得'类职责不明确'.



11->替代方案
    Hystrix作为一款开发6年之久的组件，可以满足绝大多数项目在服务容错领域的需求。如果你喜欢追逐新技术，也可以关注另外两个Hystrix的可替代方案:
    -) 'Resilience4j'
       它是Hystrix的小迷弟，是受Hystrix设计理念启发，但面向Java8和函数式编程理念的服务容错组件。它是功能完备，使用起来也很的轻巧的框架，
       甚至Hystrix的Github主页也表达了观点：Netflix在新的内部系统中尝试使用Resilience4j.

    -) 'Sentinel'
       它是SpringCloud-Alibaba组件库的成员，除了降级熔断以外，还提供了强大的限流功能（比如基于调用量，调用链路关系的限流）
       和流量整形功能（相当于流量缓冲，主动调整流量速率的功能）.
